////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2017-2018 Qualcomm Technologies, Inc.
// All Rights Reserved.
// Confidential and Proprietary - Qualcomm Technologies, Inc.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @file  camxstatsparsenode.cpp
/// @brief Implements the node to parse stats
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "camximagebuffer.h"
#include "camxstatscommon.h"
#include "camxstatsparsenode.h"

CAMX_NAMESPACE_BEGIN

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// StatsParseNode::GetFrameStatsOutput
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID* StatsParseNode::GetFrameStatsOutput(
    ISPStatsType statsType,
    UINT64       requestId)
{
    VOID* pOutput = NULL;

    // Round down to the last filled stats to republish during skipParse
    if (0 != (requestId % m_skipPattern))
    {
        requestId -= (requestId % m_skipPattern);
    }

    switch (statsType)
    {
        case ISPStatsTypeHDRBE:
            pOutput = &m_pHDRBEStatsOutput[requestId % MaxPerFramePoolWindowSize];
            break;
        case ISPStatsTypeHDRBHist:
            pOutput = &m_pHDRBHistStatsOutput[requestId % MaxPerFramePoolWindowSize];
            break;
        case ISPStatsTypeAWBBG:
            pOutput = &m_pAWBBGStatsOutput[requestId % MaxPerFramePoolWindowSize];
            break;
        case ISPStatsTypeRS:
            pOutput = &m_pRSStatsOutput[requestId % MaxPerFramePoolWindowSize];
            break;
        case ISPStatsTypeBHist:
            pOutput = &m_pBHistStatsOutput[requestId % MaxPerFramePoolWindowSize];
            break;
        case ISPStatsTypeTintlessBG:
            pOutput = &m_pTintlessBGStatsOutput[requestId % MaxPerFramePoolWindowSize];
            break;
        default:
            CAMX_LOG_ERROR(CamxLogGroupHWL, "Invalid stats type input %d", statsType);
    }
    return pOutput;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// StatsParseNode::PrepareStatsProcessRequestData
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult StatsParseNode::PrepareStatsProcessRequestData(
    ExecuteProcessRequestData*    pExecuteProcessRequestData,
    StatsParseProcessRequestData* pStatsProcessRequestData)
{
    CamxResult              result           = CamxResultSuccess;
    NodeProcessRequestData* pNodeRequestData = pExecuteProcessRequestData->pNodeProcessRequestData;
    PerRequestActivePorts*  pEnabledPorts    = pExecuteProcessRequestData->pEnabledPortsInfo;
    UINT64                  requestId        = pNodeRequestData->pCaptureRequest->requestId;

    CAMX_ASSERT_MESSAGE(NULL != pNodeRequestData, "Node request data NULL pointer");
    CAMX_ASSERT_MESSAGE(NULL != pEnabledPorts, "Per Request Active Ports NULL pointer");
    CAMX_ASSERT_MESSAGE(NULL != pNodeRequestData->pCaptureRequest, "Node capture request data NULL pointer");

    // A dependency can be reported without any actual dependencies and invocation will happen immediately
    // This helps simplify the special handling of the first requests by using the skipParse parameter to StatsParser
    if (m_bufferOffset >= requestId)
    {
        pEnabledPorts = &m_defaultPorts;
    }

    for (UINT i = 0; i < pEnabledPorts->numInputPorts; i++)
    {
        PerRequestInputPortInfo* pPerRequestInputPort = &pEnabledPorts->pInputPorts[i];

        CAMX_ASSERT_MESSAGE(NULL != pPerRequestInputPort, "Per Request Input Port NULL pointer");

        pStatsProcessRequestData->bufferInfo[i].statsType = StatsUtil::GetStatsType(pPerRequestInputPort->portId);
        pStatsProcessRequestData->bufferInfo[i].pBuffer   = pPerRequestInputPort->pImageBuffer;
        pStatsProcessRequestData->bufferInfo[i].phFences  = pPerRequestInputPort->phFence;
    }

    pStatsProcessRequestData->requestId   = requestId;
    pStatsProcessRequestData->bufferCount = pEnabledPorts->numInputPorts;

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// StatsParseNode::GetBufferDependencies
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult StatsParseNode::GetBufferDependencies(
    ExecuteProcessRequestData*  pExecuteProcessRequestData)
{
    CAMX_ENTRYEXIT(CamxLogGroupStats);
    CAMX_ASSERT_MESSAGE(NULL != pExecuteProcessRequestData, "Execute Process Request Data NULL pointer");

    PerRequestActivePorts*  pEnabledPorts    = pExecuteProcessRequestData->pEnabledPortsInfo;
    NodeProcessRequestData* pNodeRequestData = pExecuteProcessRequestData->pNodeProcessRequestData;
    UINT64                  requestId        = pNodeRequestData->pCaptureRequest->requestId;

    CAMX_ASSERT_MESSAGE(NULL != pNodeRequestData, "Node capture request data NULL pointer");
    CAMX_ASSERT_MESSAGE(NULL != pEnabledPorts, "Per Request Active Ports NULL pointer");

    // A dependency can be reported without any actual dependencies and invocation will happen immediately
    // This helps simplify the special handling of the first requests by using the skipParse parameter to StatsParser
    if (m_bufferOffset >= requestId)
    {
        pEnabledPorts = &m_defaultPorts;
    }

    // Each port gets its own dependency so that processing for each buffer happens in parallel if available
    for (UINT portIndex = 0; portIndex < pEnabledPorts->numInputPorts; portIndex++)
    {
        PerRequestInputPortInfo* pPerRequestInputPort = &pEnabledPorts->pInputPorts[portIndex];
        CAMX_ASSERT_MESSAGE(NULL != pPerRequestInputPort, "Per Request Input Port NULL pointer");

        UINT dep = pNodeRequestData->numDependencyLists;

        if (m_bufferOffset < requestId)
        {
            pNodeRequestData->dependencyInfo[dep].bufferDependency.phFences[0] =
                pPerRequestInputPort->phFence;
            pNodeRequestData->dependencyInfo[dep].bufferDependency.pIsFenceSignaled[0] =
                pPerRequestInputPort->pIsFenceSignaled;
            pNodeRequestData->dependencyInfo[dep].bufferDependency.fenceCount++;
        }

        // This will represent which buffer is ready for processing. Avoid special case 0
        pNodeRequestData->dependencyInfo[dep].processSequenceId = dep + 1;
        pNodeRequestData->dependencyInfo[dep].dependencyFlags.hasBufferDependency = TRUE;

        pNodeRequestData->numDependencyLists++;
    }

    return CamxResultSuccess;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// StatsParseNode::ExecuteProcessRequest
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult StatsParseNode::ExecuteProcessRequest(
    ExecuteProcessRequestData* pExecuteProcessRequestData)
{
    CAMX_ASSERT(NULL != pExecuteProcessRequestData);
    CAMX_ASSERT(NULL != pExecuteProcessRequestData->pNodeProcessRequestData);
    CAMX_ASSERT(NULL != pExecuteProcessRequestData->pNodeProcessRequestData->pCaptureRequest);

    CamxResult                   result                  = CamxResultSuccess;
    NodeProcessRequestData*      pNodeRequestData        = pExecuteProcessRequestData->pNodeProcessRequestData;
    UINT64                       requestId               = pNodeRequestData->pCaptureRequest->requestId;
    StatsParseProcessRequestData statsProcessRequestData = { 0 };

    pNodeRequestData->numDependencyLists = 0;

    CAMX_LOG_VERBOSE(CamxLogGroupStats, "ExecuteProcessRequest %u request id %llu",
        pNodeRequestData->processSequenceId, requestId);

    PrepareStatsProcessRequestData(pExecuteProcessRequestData, &statsProcessRequestData);

    if (0 == pNodeRequestData->processSequenceId)
    {
        result = GetBufferDependencies(pExecuteProcessRequestData);

        m_aBuffersCompleted[requestId % NumProcReqData] = pNodeRequestData->numDependencyLists;
    }

    if (0 < pNodeRequestData->processSequenceId)
    {
        // Skip if in skip pattern request or before we have valid buffers
        BOOL         skipParse = ((0 == (requestId % m_skipPattern)) && (m_bufferOffset < requestId)) ? FALSE : TRUE;
        UINT         i         = pNodeRequestData->processSequenceId - 1;
        ISPStatsType statsType = statsProcessRequestData.bufferInfo[i].statsType;
        VOID*        pOutput   = GetFrameStatsOutput(statsType, requestId);
        VOID*        pBuffer   = (NULL == statsProcessRequestData.bufferInfo[i].pBuffer) ? NULL :
                                 statsProcessRequestData.bufferInfo[i].pBuffer->GetPlaneVirtualAddr(0, 0);
        ParseData    parseData = {this, skipParse, pBuffer, pOutput};

        if (NULL != statsProcessRequestData.bufferInfo[i].pBuffer)
        {
            result = statsProcessRequestData.bufferInfo[i].pBuffer->CacheOps(TRUE, FALSE);
        }

        if (CamxResultSuccess != result)
        {
            CAMX_LOG_ERROR(CamxLogGroupAF, "Failed to invalidate cached stat buffer.");
        }
        else
        {
            result = m_pStatsParser->Parse(statsType, &parseData);
        }

        if (CamxResultSuccess != result)
        {
            CAMX_LOG_ERROR(CamxLogGroupStats, "Failed to parse %d stats result: %s",
                statsProcessRequestData.bufferInfo[i].statsType, CamxResultStrings[result]);
        }

        // Parallel processing tracking. Once all the buffers have been individually processed report done so pipeline
        // doesnt wait for us. Ideally, as long as we have nodes consuming this data, they will block for us, but don't
        // assume it
        if (0 == CamxAtomicDecU(&m_aBuffersCompleted[requestId % NumProcReqData]))
        {
            ProcessMetadataDone(requestId);
            ProcessRequestIdDone(requestId);
        }
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// StatsParseNode::ProcessingNodeInitialize
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult StatsParseNode::ProcessingNodeInitialize(
    const NodeCreateInputData*  pCreateInputData,
    NodeCreateOutputData*       pCreateOutputData)
{
    CAMX_ENTRYEXIT_SCOPE(CamxLogGroupStats, SCOPEEventStatsProcessingNodeProcessingNodeInitialize);
    CamxResult result = CamxResultSuccess;

    pCreateOutputData->pNodeName = m_pNodeName;

    CAMX_UNREFERENCED_PARAM(pCreateInputData);

    UINT inputPortId[StatsParseInputPortMaxCount];
    UINT numInputPort = 0;

    GetAllInputPortIds(&numInputPort, &inputPortId[0]);

    for (UINT inputIndex = 0; inputIndex < numInputPort; inputIndex++)
    {
        // need request - 4 buffer
        SetInputPortBufferDelta(inputIndex, m_bufferOffset);
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// StatsParseNode::ProcessingNodeFinalizeInitialization
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult StatsParseNode::ProcessingNodeFinalizeInitialization(
    FinalizeInitializationData* pFinalizeInitializationData)
{
    CAMX_ENTRYEXIT_NAME(CamxLogGroupStats, "StatsParseNode::ProcessingNodeFinalizeInitialization");
    CamxResult result = CamxResultENoMemory;

    // Store the required input information.
    m_pThreadManager = pFinalizeInitializationData->pThreadManager;
    m_pHwContext     = pFinalizeInitializationData->pHwContext;
    m_pStatsParser   = m_pHwContext->GetStatsParser();

    m_pAWBBGStatsOutput = CAMX_NEW ParsedAWBBGStatsOutput[MaxPerFramePoolWindowSize];
    if (NULL != m_pAWBBGStatsOutput)
    {
        m_pBHistStatsOutput = CAMX_NEW ParsedBHistStatsOutput[MaxPerFramePoolWindowSize];
        if (NULL != m_pBHistStatsOutput)
        {
            m_pHDRBEStatsOutput = CAMX_NEW ParsedHDRBEStatsOutput[MaxPerFramePoolWindowSize];
            if (NULL != m_pHDRBEStatsOutput)
            {
                m_pHDRBHistStatsOutput = CAMX_NEW ParsedHDRBHistStatsOutput[MaxPerFramePoolWindowSize];
                if (NULL != m_pHDRBHistStatsOutput)
                {
                    m_pRSStatsOutput = CAMX_NEW ParsedRSStatsOutput[MaxPerFramePoolWindowSize];
                    if (NULL != m_pRSStatsOutput)
                    {
                        m_pTintlessBGStatsOutput = CAMX_NEW ParsedTintlessBGStatsOutput[MaxPerFramePoolWindowSize];
                        if (NULL != m_pTintlessBGStatsOutput )
                        {
                            result = CamxResultSuccess;
                        }
                    }
                }
            }
        }
    }

    if (CamxResultSuccess != result)
    {
        CAMX_LOG_ERROR(CamxLogGroupStats, "Unable to allocate buffers for Parser output");
    }
    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// StatsParseNode::~StatsParseNode
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
StatsParseNode::~StatsParseNode()
{
    if (NULL != m_defaultPorts.pInputPorts)
    {
        CAMX_FREE(m_defaultPorts.pInputPorts);
        m_defaultPorts.pInputPorts = NULL;
    }

    if (NULL != m_pAWBBGStatsOutput)
    {
        CAMX_DELETE[] m_pAWBBGStatsOutput;
        m_pAWBBGStatsOutput = NULL;
    }

    if (NULL != m_pBHistStatsOutput)
    {
        CAMX_DELETE[] m_pBHistStatsOutput;
        m_pBHistStatsOutput = NULL;
    }

    if (NULL != m_pHDRBEStatsOutput)
    {
        CAMX_DELETE[] m_pHDRBEStatsOutput;
        m_pHDRBEStatsOutput = NULL;
    }

    if (NULL != m_pHDRBHistStatsOutput)
    {
        CAMX_DELETE[] m_pHDRBHistStatsOutput;
        m_pHDRBHistStatsOutput = NULL;
    }

    if (NULL != m_pRSStatsOutput)
    {
        CAMX_DELETE[] m_pRSStatsOutput;
        m_pRSStatsOutput = NULL;
    }

    if (NULL != m_pTintlessBGStatsOutput)
    {
        CAMX_DELETE[] m_pTintlessBGStatsOutput;
        m_pTintlessBGStatsOutput = NULL;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// StatsParseNode::StatsParseNode
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
StatsParseNode::StatsParseNode(
    const NodeCreateInputData* pCreateInputData)
{
    m_pNodeName                  = "StatsParse";
    m_bufferOffset               = GetStaticSettings()->maxPipelineDelay;
    m_parallelProcessRequests    = TRUE;
    m_derivedNodeHandlesMetaDone = TRUE;
    m_skipPattern                = 1;

    for (UINT32 i = 0; i < pCreateInputData->pNodeInfo->nodePropertyCount; i++)
    {
        if (NodePropertyStatsSkipPattern == pCreateInputData->pNodeInfo->pNodeProperties[i].id)
        {
            m_skipPattern = *static_cast<UINT*>(pCreateInputData->pNodeInfo->pNodeProperties[i].pValue);
        }
    }

    // SkipParse will result in no parsing of any of these buffer types, but we'll publish default properties for them
    static const UINT DefaultPorts[] =
    {
        StatsParseInputPortHDRBE,
        StatsParseInputPortAWBBG,
        StatsParseInputPortHDRBHist,
        StatsParseInputPortBHist,
        StatsParseInputPortRS,
        StatsParseInputPortTintlessBG
    };

    m_defaultPorts.pInputPorts = reinterpret_cast<PerRequestInputPortInfo*>(
                                    CAMX_CALLOC(sizeof(PerRequestInputPortInfo) * CAMX_ARRAY_SIZE(DefaultPorts)));

    if (NULL == m_defaultPorts.pInputPorts)
    {
        CAMX_LOG_ERROR(CamxLogGroupStats, "Unable to allocate default input port array information");
    }
    else
    {
        m_defaultPorts.numInputPorts = 0;
        for (UINT stat = 0; stat < CAMX_ARRAY_SIZE(DefaultPorts); stat++)
        {
            m_defaultPorts.pInputPorts[m_defaultPorts.numInputPorts++].portId = DefaultPorts[stat];
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// StatsParseNode::Create
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
StatsParseNode* StatsParseNode::Create(
    const NodeCreateInputData* pCreateInputData,
    NodeCreateOutputData*      pCreateOutputData)
{
    CAMX_ENTRYEXIT_NAME(CamxLogGroupStats, "StatsParseNodeCreate");
    CAMX_UNREFERENCED_PARAM(pCreateOutputData);

    return CAMX_NEW StatsParseNode(pCreateInputData);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// StatsParseNode::Destroy
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID StatsParseNode::Destroy()
{
    CAMX_DELETE this;
}

CAMX_NAMESPACE_END
